﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using EnvDTE;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio;
using EnvDTE80;
using VS_VCProject = Microsoft.VisualStudio.VCProjectEngine.VCProject;

namespace Gauss.Isp
{
    public partial class ConfigurationDialog : Form
    {
        private int _numberProcesses;
        private ProjectManager _projectManager;
        private ItemCheckEventHandler _compileProjectsCheckEventHandler;

        public int NumberProcesses
        {
            get { return _numberProcesses; }
            set { numNumberProcesses.Value = value; _numberProcesses = value; }
        }

        public static List<Project> GetCheckedProjects(Solution solution)
        {
            ProjectManager pm = new ProjectManager(solution);

            List<Project> rtn = new List<Project>();
            
            // Add the items to the list in their build order.
            bool allInList;
            do
            {
                allInList = true;
                foreach (VSProject project in pm)
                {
                    if (project.Checked && !rtn.Contains(project.Project))
                    {
                        if (project.AllDependenciesInList(rtn))
                            rtn.Add(project.Project);
                        else
                            allInList = false;
                    }
                }
            } while (!allInList);

            // Add the start state to the beginning.
            foreach (VSProject project in pm)
            {
                if (project.Checked && project.StartupProject)
                {
                    if (ProjectHandler.GetProjType(project.Project) == ProjectHandler.ProjType.FS)
                    {
                        throw new ApplicationException(Resources.Message_ErrorFSNotSup);
                    }
                    rtn.Insert(0, project.Project);
                    break;
                }
            }

            return rtn;
        }

        public ConfigurationDialog(Solution solution)
        {
            InitializeComponent();

            if (solution == null || solution.Count <= 0 || solution.Projects.Count <= 0)
            {
                throw new InvalidOperationException(Resources.Message_NoProjects);
            }

            clbCompileProjects.CheckOnClick = false;
            cmbProjectToRun.DropDownStyle = ComboBoxStyle.DropDownList;
            _projectManager = new ProjectManager(solution);
            _numberProcesses = 2;
            numNumberProcesses.Value = 2;
            btnOK.Click += new EventHandler(btnOK_Click);

            foreach (VSProject project in _projectManager)
            {
                if (!project.CantCheck)
                {
                    int index = cmbProjectToRun.Items.Add(project);
                    if (project.StartupProject)
                        cmbProjectToRun.SelectedIndex = index;
                }
                clbCompileProjects.Items.Add(project, CheckState.Unchecked);
            }

            cmbProjectToRun.SelectedIndexChanged += new EventHandler(StartupProject_ItemChanged);
            _compileProjectsCheckEventHandler = new ItemCheckEventHandler(CheckedListBox_ItemChecked);
            clbCompileProjects.ItemCheck += _compileProjectsCheckEventHandler;
            RefreshCheckboxes();
        }

        private void RefreshCheckboxes()
        {
            clbCompileProjects.ItemCheck -= _compileProjectsCheckEventHandler;
            for (int i = 0; i < clbCompileProjects.Items.Count; i++)
            {
                VSProject proj = clbCompileProjects.Items[i] as VSProject;
                if (proj != null)
                {
                    clbCompileProjects.SetItemChecked(proj.Index, proj.Checked);
                    if (proj.CantCheck)
                        clbCompileProjects.SetItemCheckState(proj.Index, CheckState.Indeterminate);
                }
            }
            clbCompileProjects.ItemCheck += _compileProjectsCheckEventHandler;
        }

        private void StartupProject_ItemChanged(object sender, EventArgs e)
        {
            ComboBox list = sender as ComboBox;
            _projectManager.StartupProject = (list.SelectedItem as VSProject).Project;

            // Reset the checked states based on the new startup project.
            RefreshCheckboxes();
        }

        private void CheckedListBox_ItemChecked(object sender, ItemCheckEventArgs e)
        {
            VSProject project = _projectManager.GetProject(e.Index);
            if (project == null)
                return;

            if (e.CurrentValue == CheckState.Checked)
                project.Checked = false;
            else if (e.CurrentValue == CheckState.Unchecked)
                project.Checked = true;

            // Set the new checked state for the current selection.
            if (project.CantCheck)
                e.NewValue = CheckState.Indeterminate;
            else if (project.Checked)
                e.NewValue = CheckState.Checked;
            else if (!project.Checked)
                e.NewValue = CheckState.Unchecked;

            // Reset the checked states. Need to do all of them for dependencies.
            RefreshCheckboxes();
        }

        private void btnOK_Click(object sender, EventArgs e)
        {
            this._numberProcesses = (int)numNumberProcesses.Value;
            this._projectManager.SaveStates();
        }

        private class VSProject
        {
            private Project _project;
            //public Project Project
            //{
            //    get
            //    {
            //        return _project;
            //    }
            //    set { }
            //}
            private IVsHierarchy _ivsHierarchy;
            private int _index;
            private bool _startup;
            private bool _checked;
            private bool _cantCheck;
            private bool _unloadedProject;
            private Dictionary<VSProject, int> _dependencies;
            private Dictionary<VSProject, int> _dependents;

            public int Index
            {
                get { return _index; }
                set { _index = value; }
            }

            public bool Checked
            {
                get
                {
                    if (CantCheck) return false;
                    else if (_startup) return true;
                    else if (_checked) return true;
                    else if (Dependent) return true;
                    else return false;
                }
                set { _checked = value; }
            }

            public bool CantCheck
            {
                get { return _cantCheck; }
            }

            public bool Dependent
            {
                get
                {
                    foreach (KeyValuePair<VSProject, int> proj in _dependents)
                        if (proj.Key.Checked)
                            return true;
                    return false;
                }
            }

            public bool StartupProject
            {
                get { return _startup; }
                set { _startup = value; }
            }

            public string UniqueName
            {
                get { return _project.UniqueName; }
            }

            public Project Project
            {
                get { return _project; }
            }

            public VSProject(Project project)
            {
                if (project == null)
                {
                    throw new ArgumentNullException("project");
                }

                _project = project;

                try
                {
                    string testForUnloadedProject = project.FullName;
                }
                catch
                {
                    _cantCheck = true;
                    _unloadedProject = true;
                }

                // Get the IVsHierarchy as it is needed for dependencies.
                IVsSolution solutionService = IspPackage.GetGlobalService(typeof(SVsSolution)) as IVsSolution;
                if (solutionService != null)
                    solutionService.GetProjectOfUniqueName(project.UniqueName, out _ivsHierarchy);

                // See if there is a property saying it is checked.
                try
                {
                    if (_project.Globals.get_VariableExists("ISP_Compile") &&
                        _project.Globals["ISP_Compile"] as string == "true")
                    {
                        _checked = true;
                    }
                }
                catch
                {
                }

                // See if there is a property saying it is the startup.
                try
                {
                    if (_project.Globals.get_VariableExists("ISP_Startup") &&
                        _project.Globals["ISP_Startup"] as string == "true")
                    {
                        _startup = true;
                    }
                }
                catch
                {
                }

                _dependencies = new Dictionary<VSProject, int>();
                _dependents = new Dictionary<VSProject, int>();
            }

            public void SaveState()
            {
                // Update the project file, with the checked state and startup information.
                try
                {
                    if (_checked)
                    {
                        if (!_project.Globals.get_VariablePersists("ISP_Compile") ||
                            _project.Globals["ISP_Compile"] as string != "true")
                        {
                            _project.Globals["ISP_Compile"] = "true";
                            _project.Globals.set_VariablePersists("ISP_Compile", true);
                        }
                    }
                    else
                    {
                        if (_project.Globals.get_VariablePersists("ISP_Compile") &&
                            _project.Globals["ISP_Compile"] as string != "false")
                        {
                            _project.Globals["ISP_Compile"] = "false";
                            _project.Globals.set_VariablePersists("ISP_Compile", true);
                        }
                    }

                    if (_startup)
                    {
                        if (!_project.Globals.get_VariablePersists("ISP_Startup") ||
                            _project.Globals["ISP_Startup"] as string != "true")
                        {
                            _project.Globals["ISP_Startup"] = "true";
                            _project.Globals.set_VariablePersists("ISP_Startup", true);
                        }
                    }
                    else
                    {
                        if (_project.Globals.get_VariablePersists("ISP_Startup") &&
                            _project.Globals["ISP_Startup"] as string != "false")
                        {
                            _project.Globals["ISP_Startup"] = "false";
                            _project.Globals.set_VariablePersists("ISP_Startup", true);
                        }
                    }
                }
                catch
                { }
            }

            public void ClearDependencies()
            {
                foreach (KeyValuePair<VSProject, int> project in _dependencies)
                {
                    project.Key._dependents.Remove(this);
                }
                _dependencies.Clear();
            }

            public void AddDependency(VSProject proj)
            {
                try
                {
                    _dependencies.Add(proj, 0);
                }
                catch { }
                try
                {
                    proj._dependents.Add(this, 0);
                }
                catch { }
            }

            public void AddDependencies(Dictionary<VSProject, int> projects)
            {
                foreach (KeyValuePair<VSProject, int> project in projects)
                {
                    if (project.Key != null && project.Key != this)
                    {
                        AddDependency(project.Key);
                    }
                }
            }

            public bool AllDependenciesInList(List<Project> dependencies)
            {
                foreach (KeyValuePair<VSProject, int> project in _dependencies)
                {
                    if (!dependencies.Contains(project.Key.Project))
                    {
                        return false;
                    }
                }
                return true;
            }

            private List<IVsHierarchy> GetDependentIVsHierarchies(List<IVsHierarchy> accum, IVsHierarchy project)
            {
                try
                {
                    // Add myself to the list.
                    accum.Add(project);

                    IVsSolutionBuildManager2 buildManager = IspPackage.GetGlobalService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager2;
                    if (buildManager == null)
                        return accum;

                    ErrorHandler.ThrowOnFailure(buildManager.CalculateProjectDependencies());

                    // How many dependencies are there?
                    uint[] pcActual = new uint[1];
                    ErrorHandler.ThrowOnFailure(buildManager.GetProjectDependencies(project, 0, null, pcActual));
                    if (pcActual[0] > 0)
                    {
                        // Get the dependencies.
                        IVsHierarchy[] projects = new IVsHierarchy[pcActual[0]];
                        ErrorHandler.ThrowOnFailure(buildManager.GetProjectDependencies(project, pcActual[0], projects, pcActual));

                        // Recursively add everyone else.
                        foreach (IVsHierarchy p in projects)
                        {
                            GetDependentIVsHierarchies(accum, p);
                        }
                    }
                }
                catch
                {
                }

                return accum;
            }

            public Dictionary<VSProject, int> GetDependentProjects(ProjectManager projectManager)
            {
                List<IVsHierarchy> depend = GetDependentIVsHierarchies(new List<IVsHierarchy>(), _ivsHierarchy);

                Dictionary<VSProject, int> rtn = new Dictionary<VSProject, int>();
                foreach (IVsHierarchy hier in depend)
                {
                    object project;
                    VSProject vcProject = null;
                    if (ErrorHandler.Failed(hier.GetProperty((uint)VSConstants.VSITEMID_ROOT, (int)Microsoft.VisualStudio.Shell.Interop.__VSHPROPID.VSHPROPID_ExtObject, out project)))
                        project = null;
                    if (project != null)
                    {
                        vcProject = projectManager.GetProject((project as Project).UniqueName);
                    }

                    if (vcProject != null)
                    {
                        try
                        {
                            rtn.Add(vcProject, 0);
                        }
                        catch { }
                    }
                }
                return rtn;
            }

            public IVsHierarchy GetIVsHierarchy()
            {
                return _project.Object as IVsHierarchy;
            }

            public override string ToString()
            {
                if (_unloadedProject || string.IsNullOrEmpty(_project.FullName))
                    return _project.Name;
                else
                    return Utils.StringFormat("{0} ({1})", _project.Name, _project.FullName);
            }
        }

        private class ProjectManager : IEnumerable<VSProject>
        {
            private Dictionary<string, VSProject> _projectsDictionary;
            private List<VSProject> _projectsList;
            private VSProject _startupProject;

            public Project StartupProject
            {
                get
                {
                    if (_startupProject == null)
                        return null;
                    else
                        return _startupProject.Project;
                }

                set
                {
                    _startupProject = GetProject(value.UniqueName);
                    foreach (VSProject vcProject in _projectsList)
                    {
                        if (vcProject.UniqueName == value.UniqueName)
                            vcProject.StartupProject = true;
                        else
                            vcProject.StartupProject = false;
                    }
                }
            }

            public ProjectManager(Solution solution)
            {
                _projectsDictionary = new Dictionary<string, VSProject>();
                _projectsList = new List<VSProject>();

                string startupProjectUniqueName = null;
                SolutionBuild2 solutionBuild = solution.SolutionBuild as SolutionBuild2;
                object[] startupProjects = (object[])solutionBuild.StartupProjects;
                if (startupProjects != null && startupProjects.Length > 0)
                    startupProjectUniqueName = startupProjects[0] as string;
                else
                    startupProjectUniqueName = null;

                // Add the projects.
                int numberStartupProjects = 0;
                int projIndx = 0;
                Queue<Project> projects = new Queue<Project>(); //BFS search of hierarchy 
                if (solution.Projects.Count > 0)
                {
                    foreach (Project proj in solution.Projects)
                    {
                        projects.Enqueue(proj);
                    }
                }
                while(projects.Count > 0)
                {
                    Project p = projects.Dequeue();
                    if (p.ProjectItems != null && p.ProjectItems.Count > 0)
                    {
                        foreach (ProjectItem pi in p.ProjectItems)
                        {
                            if(pi.SubProject != null)
                            {
                                projects.Enqueue(pi.SubProject);
                            }
                        }
                        //TODO: this will have to be changed to support F#
                        ProjectHandler.ProjType type = ProjectHandler.GetProjType(p);
                        if (type == ProjectHandler.ProjType.CS ||
                            type == ProjectHandler.ProjType.VC)
                        {
                            VSProject vsp = new VSProject(p);
                            vsp.Index = projIndx++;
                            if (vsp.StartupProject) numberStartupProjects++;
                            _projectsDictionary.Add(vsp.UniqueName, vsp);
                            _projectsList.Add(vsp);
                        }
                    }
                }

                // Select the startup project if one is not already selected. If there are
                // more than one startup projects, select the first one.
                if (numberStartupProjects == 0 && startupProjectUniqueName != null)
                {
                    VSProject startupProj = _projectsDictionary[startupProjectUniqueName];
                    if (startupProj != null)
                        StartupProject = startupProj.Project;
                }
                else if (numberStartupProjects > 1)
                {
                    foreach (VSProject proj in _projectsList)
                    {
                        if (proj.StartupProject)
                        {
                            StartupProject = proj.Project;
                            break;
                        }
                    }
                }

                // Setup the project dependencies.
                foreach (VSProject project in _projectsList)
                {
                    Dictionary<VSProject, int> projs = project.GetDependentProjects(this);
                    project.ClearDependencies();
                    project.AddDependencies(projs);
                }
            }

            public VSProject GetProject(string name)
            {
                if (_projectsDictionary.ContainsKey(name))
                {
                    return _projectsDictionary[name];
                }
                else
                {
                    return null;
                }
            }

            public VSProject GetProject(int index)
            {
                if (index >= 0 && index < _projectsList.Count)
                    return _projectsList[index];
                else
                    return null;
            }

            public void SaveStates()
            {
                foreach (VSProject project in _projectsList)
                {
                    project.SaveState();
                }
            }

            public IEnumerator<VSProject> GetEnumerator()
            {
                return _projectsList.GetEnumerator();
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return (_projectsList as System.Collections.IEnumerable).GetEnumerator();
            }
        }
    }
}
